package dao;

import pojo.BaseEntity;
import util.DbUtils;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 基础的抽象数据库操作类
 * 封装了基础的CRUD方法
 * @author zhuyst
 * @param <T> 要操作的实体类
 *           实体类需要继承 {@link BaseEntity}
 *           方便使用主键进行操作
 */
public abstract class BaseDao<T extends BaseEntity> {

    /**
     * 数据库的Connection，一个DAO使用一个Connection
     * 使用 {@link DbUtils#getConnection()} 获取
     */
    private Connection connection;

    /**
     * 通过SQL语句获取一个PreparedStatement
     * @param sql SQL语句
     * @return 通过SQL语句实例化的PreparedStatement
     * @throws SQLException 数据库异常
     */
    protected PreparedStatement prepareStatement(String sql) throws SQLException {
        if(connection == null){
            connection = DbUtils.getConnection();
        }
        return connection.prepareStatement(sql);
    }

    /**
     * 通过SQL语句获取一个PreparedStatement，可以指定autoGeneratedKeys
     * 具体可查看 {@link Connection#prepareStatement(String, int)}
     * 不需要指定autoGeneratedKeys可以使用 {@link #prepareStatement(String)}
     *
     * @param sql SQL语句
     * @param autoGeneratedKeys {@link Statement} 指定autoGeneratedKeys
     * @return 通过SQL语句实例化的PreparedStatement
     * @throws SQLException 数据库异常
     */
    protected PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        if(connection == null){
            connection = DbUtils.getConnection();
        }
        return connection.prepareStatement(sql,autoGeneratedKeys);
    }

    /**
     * 通过ID获取相应实体
     * @param sql 查询的SQL语句
     * @param id 要获取的实体ID
     * @return 获取到的实体，如果没有相应的实体则返回NULL
     */
    protected T selectById(String sql, int id){
        try {
            PreparedStatement preparedStatement = prepareStatement(sql);
            preparedStatement.setInt(1,id);

            ResultSet set = preparedStatement.executeQuery();
            set.next();
            return getPojo(set);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 列出全部的实体
     * @param sql 查询的SQL语句
     * @return 获取到的实体列表
     */
    protected List<T> list(String sql){
        List<T> list = new ArrayList<>();
        try {
            PreparedStatement preparedStatement = prepareStatement(sql);
            ResultSet set = preparedStatement.executeQuery();

            while(set.next()){
                T t = getPojo(set);
                list.add(t);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return list;
    }

    /**
     * 往数据库中插入一条记录
     * @param sql 新增的SQL语句
     * @param t 新增的实体类
     * @return 自增的主键ID
     */
    protected Integer insert(String sql,T t){
        try {
            PreparedStatement preparedStatement =
                    prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

            this.setPojo(preparedStatement,t);
            preparedStatement.executeUpdate();

            ResultSet set = preparedStatement.getGeneratedKeys();
            set.next();
            return set.getInt(1);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 更新数据库中的一条数据
     * @param sql 更新的SQL语句
     * @param t 更新的实体类
     * @param idIndex SQL语句中，编译ID的位置
     */
    protected void update(String sql,T t,int idIndex){
        try {
            PreparedStatement preparedStatement = prepareStatement(sql);
            this.setPojo(preparedStatement,t);
            preparedStatement.setInt(idIndex,t.getId());
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除数据库中的一条数据
     * @param sql 删除的SQL语句
     * @param id 要删除的实体的ID
     */
    protected void delete(String sql,int id){
        try {
            PreparedStatement preparedStatement = prepareStatement(sql);
            preparedStatement.setInt(1,id);
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过ID查询对应实体类
     * 应以 {@link #selectById(String, int)} 实现
     * @param id 要查询的记录的ID
     * @return 查询到的实体类，如果没有则返回NULL
     */
    public abstract T selectById(int id);

    /**
     * 查询实体列表
     * 应以 {@link #list(String)} 实现
     * @return 实体列表
     */
    public abstract List<T> list();

    /**
     * 新增一条记录
     * 应以 {@link #insert(String, BaseEntity)} 实现
     * @param t 新增的实体类
     * @return 自增的主键ID
     */
    public abstract Integer insert(T t);

    /**
     * 更新一条记录
     * 应以 {@link #update(String, BaseEntity, int)} 实现
     * @param t 更新的实体类
     */
    public abstract void update(T t);

    /**
     * 通过ID删除一条记录
     * 应以 {@link #update(String, BaseEntity, int)} 实现
     * @param id 要删除的记录的ID
     */
    public abstract void delete(int id);

    /**
     * 通过ResultSet获取、包装实体类
     * 注意在调用前要使用 {@link ResultSet#next()}
     *
     * @param set 注意游标处于有数据的位置
     * @return 获取、包装后的实体类
     * @throws SQLException 数据库异常
     */
    protected abstract T getPojo(ResultSet set) throws SQLException;

    /**
     * 往PreparedStatement中设置预编译的属性
     * @param preparedStatement 通过SQL实例化后的PreparedStatement
     *                          {@link #prepareStatement(String)},
     *                          {@link #prepareStatement(String, int)}
     * @param t 要往PreparedStatement设置的实体类
     * @throws SQLException 数据库异常
     */
    protected abstract void setPojo(PreparedStatement preparedStatement,T t) throws SQLException;
}
